package com.ubxtech.db.handler;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.ubxtech.core.context.ContextUtil;
import com.ubxtech.core.generator.IdGenerator;
import com.ubxtech.core.utils.SpringUtils;
import com.ubxtech.core.utils.StrPool;
import com.ubxtech.db.entity.Entity;
import com.ubxtech.db.entity.CreateEntity;
import com.ubxtech.db.entity.IdEntity;
import com.ubxtech.db.entity.TimeEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.Date;

/**
 * @author Victor.Xiao
 * @since 2022-01-07 16:38
 **/
@Slf4j
@Component
public class BaseEntityMetaObjectHandler implements MetaObjectHandler {


    private IdGenerator idGenerator;


    /**
     * 注意：不支持 复合主键 自动注入！！
     * <p>
     * 1、所有的继承了Entity、SuperEntity的实体，在insert时，
     * id： id为空时， 通过IdGenerate生成唯一ID。
     * createdBy, updatedBy: 自动赋予 当前线程上的登录人id。
     * createTime, updateTime: 自动赋予 服务器的当前时间。
     * <p>
     * 注意：实体中字段为空时才会赋值，若手动传值了，这里不会重新赋值
     * <p>
     * 2、未继承任何父类的实体，且主键标注了 @TableId(value = "xxx", type = IdType.INPUT) 也能自动赋值，主键的字段名称任意
     * <p>
     * 3、未继承任何父类的实体，但主键名为 id，也能自动赋值
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        /*
         * 创建人 自动赋予 当前线程上的登录人id
         * 创建时间  自动赋予 服务器的当前时间
          */
        fillBy(metaObject,StrPool.CREATED_BY);
        fillTime(metaObject,StrPool.CREATE_TIME);
        /*
         * 修改人 自动赋予 当前线程上的登录人id
         * 修改时间  自动赋予 服务器的当前时间
         */
        fillTime(metaObject,StrPool.UPDATE_TIME);
        fillBy(metaObject,StrPool.UPDATED_BY);
        // id
        fillId(metaObject);
    }

    /**
     * 所有的继承了Entity、SuperEntity的实体，在update时，
     * updatedBy: 自动赋予 当前线程上的登录人id
     * updateTime: 自动赋予 服务器的当前时间
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        if (log.isDebugEnabled()) {
            log.debug("start update fill ....");
        }
        /*
         * 修改人 自动赋予 当前线程上的登录人id
         * 修改时间  自动赋予 服务器的当前时间
         */
        fillTime(metaObject,StrPool.UPDATE_TIME);
        fillBy(metaObject,StrPool.UPDATED_BY);
    }

    private void fillId(MetaObject metaObject) {
        if (idGenerator == null) {
            // 这里使用SpringUtils的方式"异步"获取对象，防止启动时，报循环注入的错
            idGenerator = SpringUtils.getBean(IdGenerator.class);
        }
        Long id = idGenerator.getUid();
        if ( id == -1) {
            return ;
        }

        //1. 继承了SuperEntity 若 ID 中有值，就不设置
        if (metaObject.getOriginalObject() instanceof IdEntity) {
            Object oldId = ((IdEntity) metaObject.getOriginalObject()).getId();
            if (oldId != null && !StrUtil.isBlank(oldId.toString())) {
                return;
            }
            Object idVal = StrPool.STRING_TYPE_NAME.equals(metaObject.getGetterType(StrPool.FIELD_ID).getName()) ? String.valueOf(id) : id;
            this.setFieldValByName(StrPool.FIELD_ID, idVal, metaObject);
            return;
        }

        // 2. 没有继承SuperEntity， 但主键的字段名为：  id
        if (metaObject.hasGetter(StrPool.FIELD_ID)) {
            Object oldId = metaObject.getValue(StrPool.FIELD_ID);
            if (oldId != null && !StrUtil.isBlank(oldId.toString())) {
                return;
            }

            Object idVal = StrPool.STRING_TYPE_NAME.equals(metaObject.getGetterType(StrPool.FIELD_ID).getName()) ? String.valueOf(id) : id;
            this.setFieldValByName(StrPool.FIELD_ID, idVal, metaObject);
            return;
        }

        // 3. 实体没有继承 Entity 和 SuperEntity，且 主键名为其他字段
        TableInfo tableInfo = TableInfoHelper.getTableInfo(metaObject.getOriginalObject().getClass());
        if (tableInfo == null) {
            return;
        }
        // 主键类型
        Class<?> keyType = tableInfo.getKeyType();
        if (keyType == null) {
            return;
        }
        // id 字段名
        String keyProperty = tableInfo.getKeyProperty();
        Object oldId = metaObject.getValue(keyProperty);
        if (oldId != null && !StrUtil.isBlank(oldId.toString())) {
            return;
        }

        // 反射得到 主键的值
        Field idField = ReflectUtil.getField(metaObject.getOriginalObject().getClass(), keyProperty);
        Object fieldValue = ReflectUtil.getFieldValue(metaObject.getOriginalObject(), idField);
        // 判断ID 是否有值，有值就不
        if (ObjectUtil.isNotEmpty(fieldValue)) {
            return;
        }
        Object idVal = keyType.getName().equalsIgnoreCase(StrPool.STRING_TYPE_NAME) ? String.valueOf(id) : id;
        this.setFieldValByName(keyProperty, idVal, metaObject);
    }



    private void update(MetaObject metaObject) {
        Entity entity = (Entity) metaObject.getOriginalObject();
        fillBy(metaObject,StrPool.UPDATED_BY);
        fillTime(metaObject,StrPool.UPDATE_TIME);
//        if (entity.getModifiedTime() == null) {
//            ParameterizedType parameterizedType = (ParameterizedType)metaObject.getOriginalObject().getClass().getGenericSuperclass();
//            String typeName = parameterizedType.getActualTypeArguments()[2].getTypeName();
//            if (typeName.contains(StrPool.DATE)) {
//                this.setFieldValByName(StrPool.UPDATE_TIME, new Date(), metaObject);
//            } else if (typeName.contains(StrPool.LONG)) {
//                this.setFieldValByName(StrPool.UPDATE_TIME, System.currentTimeMillis(), metaObject);
//            }
//        }
    }

    private void fillBy(MetaObject metaObject, String fieldName) {
        if (!metaObject.hasGetter(fieldName)) {
            return;
        }
        Object oldVal = metaObject.getValue(fieldName);
        if (oldVal == null || StrUtil.isBlank(String.valueOf(oldVal)) || StrPool.ZERO.equalsIgnoreCase(oldVal.toString())) {
            String userId = ContextUtil.getContextToken().getUserId();
            if (StrUtil.isNotBlank(userId)) {
                this.setFieldValByName(fieldName, userId, metaObject);
            }
        }
    }

    private void fillTime(MetaObject metaObject, String fieldName) {
        if (!metaObject.hasGetter(fieldName)) {
            return;
        }
        Object oldVal = metaObject.getValue(fieldName);
        if (oldVal == null) {
            if (metaObject.getGetterType(fieldName).toString().contains(StrPool.LONG)) {
                this.setFieldValByName(fieldName, System.currentTimeMillis(), metaObject);
            } else if (metaObject.getGetterType(fieldName).toString().contains(StrPool.LOCAL_DATE_TIME_TYPE_NAME)){
                this.setFieldValByName(fieldName, LocalDateTime.now(), metaObject);
            } else if (metaObject.getGetterType(fieldName).toString().contains(StrPool.LOCAL_DATE_TYPE_NAME)){
                this.setFieldValByName(fieldName, LocalDate.now(), metaObject);
            } else if (metaObject.getGetterType(fieldName).toString().contains(StrPool.LOCAL_ZONE_DATE_TYPE_NAME)){
                this.setFieldValByName(fieldName, ZonedDateTime.now(), metaObject);
            } else {
                this.setFieldValByName(fieldName, new Date(), metaObject);
            }
        }
    }
}
